#include "factory.h"

__thread Client_State_t cur_client;

void factory_init(Factory_t *pf, int thread_num, int capacity)
{
    que_init(&pf->que, capacity);
    pthread_cond_init(&pf->cond, NULL);
    pf->pthid = (pthread_t *)calloc(thread_num, sizeof(pthread_t));
    pf->flag = 0;
}

void factory_start(Factory_t *pf, int thread_num)
{
    if (pf->flag != 1)
    {
        for (int i = 0; i < thread_num; i++)
        {
            pthread_create(pf->pthid + i, NULL, thread_handle, pf);
        }
        pf->flag = 1;
    }
}

void cleanup_handler(void *arg)
{
    pthread_mutex_unlock((pthread_mutex_t *)arg);
}

void *thread_handle(void *p)
{
#ifdef DEBUG
    // printf("thread thid = %ld\n", syscall(SYS_gettid));
    printf("thread thid = %ld\n", pthread_self());
#endif
    Factory_t *pf = (Factory_t *)p;                 // 线程池
    pQue_t pq = &pf->que;                           // 客户队列
    pNode_t pcur = (pNode_t)malloc(sizeof(Node_t)); // 要从客户队列拿的任务

    while (1)
    {
        pthread_mutex_lock(&pq->mutex);
        pthread_cleanup_push(cleanup_handler, &pq->mutex);

        if (pq->que_size == 0) // 队列为空就睡觉
        {
            pthread_cond_wait(&pf->cond, &pq->mutex); // 睡觉的时候锁会解开
        }
        printf("thread thid = %ld, ready to get task\n", pthread_self());
        que_get(pq, &pcur); // 把耗时的free(pcur)操作拿到外面去做
        pthread_cleanup_pop(1);

        if (pcur != NULL)
        {
#ifdef DEBUG
            // printf("thread thid = %ld\n", syscall(SYS_gettid));
            printf("I am thread thid = %ld, get task = %d\n", pthread_self(), pcur->new_fd);
#endif
            do_reception(pcur->new_fd);
            free(pcur);
            pcur = NULL;
        }
    }
}

int do_reception(int new_fd)
{
    train_state_t t_s_data;
    test_script(new_fd);
    while (1)
    {
        memset(&t_s_data, 0, sizeof(t_s_data));
        int ret = recv(new_fd, &t_s_data, sizeof(t_s_data), 0);
        // 客户端断开
        if (ret == 0)
        {
            printf("client %d leaved.\n", new_fd);
            close(new_fd);
            break;
        }
        printf("recv's ret = %d, get command: %s\n", ret, t_s_data.buf);

        // switch(t_s_data.state)
        if (t_s_data.state == REGISTER)
        {
            exec_register(t_s_data, new_fd);
        }
        else if (t_s_data.state == LOGIN)
        {
            exec_login(t_s_data, new_fd);
        }
        else
        {
            exec_after_login(t_s_data, new_fd);
        }
    }
}
int test_script(int new_fd)
{
    memset(&cur_client, 0, sizeof(cur_client));
    cur_client.new_fd = new_fd;
    sprintf(cur_client.path, "%s%s%s", USERSPACE, "zzz", "/");
    cur_client.parent_code = 0;
    cur_client.user_id = 4062;
}

// 用户注册
int exec_register(train_state_t t_s_data, int new_fd)
{
    // 验证
    char username[100] = {0};
    char raw_password[100] = {0};
    char *encry_password;
    sscanf(t_s_data.buf, "%s %s", username, raw_password);
    printf("get username = %s\n", username);
    char salt[20] = {0};
    generateSalt(salt);
    encry_password = crypt(raw_password, salt);

    char pwd[100] = {0};
    sprintf(pwd, "/netdisk/%s/", username);

    // 回复
    train_t r_data;
    memset(&r_data, 0, sizeof(r_data));
    r_data.data_len = insert_to_userTable(username, salt, encry_password, pwd);
    if (r_data.data_len != 0)
    {
        strcpy(r_data.buf, "user register failed");
    }
    else
    {
        // 创建用户文件夹
        char user_path[100];
        sprintf(user_path, "mkdir %s%s", USERSPACE, username);
        system(user_path);
        strcpy(r_data.buf, "user register success");
    }
    send(new_fd, &r_data, sizeof(r_data), 0);
    return 0;
}

// 用户登录
int exec_login(train_state_t t_s_data, int new_fd)
{
    // 验证
    int user_id;
    char username[100] = {0};
    char raw_password[100] = {0};
    char salt[20] = {0};
    char *encry_password_from_client;
    sscanf(t_s_data.buf, "%s %s", username, raw_password);

    // 回复
    train_t r_data;
    memset(&r_data, 0, sizeof(r_data));
    char encry_password_from_server[100];
    int ret = select_to_login(username, salt, encry_password_from_server, &user_id);
    if (ret == -1)
    {
        r_data.data_len = 0;
        strcpy(r_data.buf, "login failed");
        send(new_fd, &r_data, sizeof(r_data), 0);
        return 0;
    }
    printf("username = %s, passwd = %s\n", username, raw_password);
    encry_password_from_client = crypt(raw_password, salt);
    printf("salt = %s\nclient passwd = %s\n server passwd = %s\n", salt, encry_password_from_client, encry_password_from_server);

    r_data.data_len = strcmp(encry_password_from_client, encry_password_from_server);
    if (r_data.data_len != 0)
    {
        strcpy(r_data.buf, "login failed");
    }
    else
    {
        strcpy(r_data.buf, "login success");
    }
    send(new_fd, &r_data, sizeof(r_data), 0);

    memset(&cur_client, 0, sizeof(cur_client));
    cur_client.new_fd = new_fd;
    sprintf(cur_client.path, "%s%s%s", USERSPACE, username, "/");
    cur_client.parent_code = 0;
    cur_client.user_id = user_id;

    return 0;
}

// 登录后的其他命令
int exec_after_login(train_state_t t_s_data, int new_fd)
{
    train_t r_data;
    memset(&r_data, 0, sizeof(r_data));

    if (strlen(cur_client.path) != 0)
    {
        switch (t_s_data.state)
        {
        case CD:
            exec_cd(t_s_data);
            break;
        case LS:
            exec_ls(t_s_data);
            break;
        case PWD:
            exec_pwd(t_s_data);
            break;
        case REMOVE:
            exec_remove(t_s_data);
            break;
        case GETS:
            exec_gets(t_s_data);
            break;
        case PUTS:
            exec_puts(t_s_data);
            break;
        }
    }
    else
    {
        r_data.data_len = -1;
        strcpy(r_data.buf, "please login first");
        send(new_fd, &r_data, sizeof(r_data), 0);
    }
}

int exec_cd(train_state_t t_s_data)
{
    train_t r_data;
    memset(&r_data, 0, sizeof(r_data));

    r_data.data_len = select_dir_by_dirname(t_s_data.buf + 3, cur_client.user_id, &cur_client.parent_code);
    if (r_data.data_len == -1)
    {
        strcpy(r_data.buf, "wrong floder name");
        send(cur_client.new_fd, &r_data, sizeof(r_data), 0);
    }
    else
    {
        if (!strcmp(t_s_data.buf + 3, ".."))
        {
            char *p = strrchr(cur_client.path, '/');
            *p = '\0';
            p = strrchr(cur_client.path, '/');
            p ++;
            *p = '\0';
        }
        else
        {
            strcat(cur_client.path, t_s_data.buf + 3);
            strcat(cur_client.path, "/");
        }
        exec_pwd(t_s_data);
    }
}

int exec_ls(train_state_t t_s_data)
{
    train_t r_data;
    memset(&r_data, 0, sizeof(r_data));

    // 找到倒数第二个'\'
    char p[100];
    strcpy(p, cur_client.path);

    select_allfile_by_userid(r_data.buf, cur_client.user_id, cur_client.parent_code);

    send(cur_client.new_fd, &r_data, sizeof(r_data), 0);
}

int exec_pwd(train_state_t t_s_data)
{
    train_t r_data;
    memset(&r_data, 0, sizeof(r_data));
    char username[20] = {0};
    select_username_by_userid(username, cur_client.user_id);

    sprintf(r_data.buf, "%s", cur_client.path + strlen(USERSPACE) + strlen(username));
    send(cur_client.new_fd, &r_data, sizeof(r_data), 0);
}

int exec_remove(train_state_t t_s_data)
{
    train_t r_data;
    memset(&r_data, 0, sizeof(r_data));

    r_data.data_len = delete_file_by_filename(t_s_data.buf + 7, cur_client.user_id, cur_client.parent_code);
    if (r_data.data_len == -1)
    {
        strcpy(r_data.buf, "delete faield");
    }
    else
    {
        strcpy(r_data.buf, "delete success");
    }
    send(cur_client.new_fd, &r_data, sizeof(r_data), 0);
}


int exec_gets(train_state_t t_s_data)
{
    train_t r_data;
    memset(&r_data, 0, sizeof(r_data));

    char filetype[10] = {0};
    r_data.data_len = select_fileinfo_by_filename(t_s_data.buf + 5, cur_client.parent_code, filetype);
    if (r_data.data_len == -1)
    {
        strcpy(r_data.buf, "no such file");
        send(cur_client.new_fd, &r_data, sizeof(r_data), 0);
    }
    else
    {
        if (!strcmp(filetype, "d"))
        {
            r_data.data_len = -1;
            strcpy(r_data.buf, "can not gets a folder");
            send(cur_client.new_fd, &r_data, sizeof(r_data), 0);
        }
        else
        {
            sprintf(r_data.buf, "ready to send file %s", t_s_data.buf + 5);
            send(cur_client.new_fd, &r_data, sizeof(r_data), 0);
            upload_file(cur_client.new_fd, t_s_data.buf + 5);
        }
    }
}

int exec_puts(train_state_t t_s_data)
{
    train_t r_data;
    memset(&r_data, 0, sizeof(r_data));

    int ret = download_file(cur_client.new_fd);
    if (ret == 0) 
    {
        strcpy(r_data.buf, "puts success");
    }
    else
    {
        r_data.data_len = -1;
        strcpy(r_data.buf, "puts failed");
    }

    send(cur_client.new_fd, &r_data, sizeof(r_data), 0);

    
}

// 生成盐值
int generateSalt(char *str)
{
    memset(str, 0, strlen(str));
    int i, flag;
    srand(time(NULL));
    for (i = 0; i < 8; i++)
    {
        flag = rand() % 3;
        switch (flag)
        {
        case 0:
            str[i] = rand() % 26 + 'a';
            break;
        case 1:
            str[i] = rand() % 26 + 'A';
            break;
        case 2:
            str[i] = rand() % 10 + '0';
            break;
        }
    }
}

int Compute_file_md5(const char *file_path, char *md5_str)
{
	int i;
	int fd;
	int ret;
	unsigned char data[READ_DATA_SIZE];
	unsigned char md5_value[MD5_SIZE];
	MD5_CTX md5;
 
	fd = open(file_path, O_RDONLY);
	if (-1 == fd)
	{
		perror("open");
		return -1;
	}
 
	// init md5
	MD5Init(&md5);
 
	while (1)
	{
		ret = read(fd, data, READ_DATA_SIZE);
		if (-1 == ret)
		{
			perror("read");
			return -1;
		}
 
		MD5Update(&md5, data, ret);
 
		if (0 == ret || ret < READ_DATA_SIZE)
		{
			break;
		}
	}
 
	close(fd);
 
	MD5Final(&md5, md5_value);
 
	for(i = 0; i < MD5_SIZE; i++)
	{
		snprintf(md5_str + i*2, 2+1, "%02x", md5_value[i]);
	}
	md5_str[MD5_STR_LEN] = '\0'; // add end
 
	return 0;
}

int download_file(int remote_fd)
{
    int ret;
    int data_len;
    char buf[1000]={0};

    struct timeval start, end;
    gettimeofday(&start, NULL);
    

    // 1. 接收文件名
    recv_n(remote_fd, &data_len, 4); // 接收文件名长度
    recv_n(remote_fd, buf, data_len); // 接收文件名
    char real_filename[100] = {0};
    sprintf(real_filename, "%s%s", cur_client.path, buf);
    printf("create file %s\n", real_filename);
    int fd = open(real_filename,O_RDWR|O_CREAT,0666); // 创建新文件

    // 2. 接收文件大小
    off_t file_size;
    recv_n(remote_fd, &data_len, 4);
    recv_n(remote_fd, &file_size, sizeof(file_size));

    // 3. 接收文件内容，使用splice
    int pipefd[2] = {0};
    pipe(pipefd);
    off_t download_size = 0, last_size = 0, slipce_size;
    slipce_size = data_len / 10000; // 文件0.01%大小
    off_t splice_byte;
    off_t remain_size = 32768;

    while(download_size < file_size)
    {
        if (remain_size > (file_size - download_size))
        {
            remain_size = file_size - download_size;
        }
        // 接收文件数据到管道
        splice_byte = splice(remote_fd, NULL, pipefd[1], NULL, remain_size, SPLICE_F_MOVE | SPLICE_F_MORE);
        // 将接收到的数据从管道写到文件里
        ret = splice(pipefd[0], NULL, fd, NULL, splice_byte, SPLICE_F_MOVE | SPLICE_F_MORE);

        download_size += ret;
        if (download_size - last_size > slipce_size)
        {
            printf("%6.2lf%%\r", (double)download_size / file_size * 100);
            fflush(stdout);
            last_size = download_size;
        } 
    }

    gettimeofday(&end, NULL);
    printf("use time = %ld\n", (end.tv_sec - start.tv_sec) * 1000000 + end.tv_usec - start.tv_usec);
    close(fd);

    char md5_str[100] = {0};
    Compute_file_md5(real_filename, md5_str);
    ret = insert_to_virualFileTable(buf,  md5_str, file_size);
    if (ret != -1)
    {
        return 0;
    }
    return -1;
}

// 发送文件
int upload_file(int remote_fd, char *file_name)
{
    // 1. 发送文件名
    train_t t;
    t.data_len = strlen(file_name);
    strcpy(t.buf, file_name);
    send_n(remote_fd, &t, 4 + t.data_len);

    // 2. 发送文件大小
    char real_filename[100] = {0};
    sprintf(real_filename, "%s%s", cur_client.path, file_name);
    struct stat file_info;
    stat(real_filename, &file_info);
    t.data_len = sizeof(file_info.st_size);
    memcpy(t.buf, &file_info.st_size, sizeof(file_info.st_size));
    send_n(remote_fd, &t, 4 + t.data_len);

    // 3. 发送文件内容
    int fd = open(real_filename, O_RDONLY);
    sendfile(remote_fd, fd, NULL, file_info.st_size);
}

int recv_n(int sfd, void *buf, int data_len)
{
    char *p = (char *)buf;
    int total = 0, ret;

    while (total < data_len)
    {
        ret = recv(sfd, p + total, data_len - total, 0);
        if (ret == 0) // 服务器断开
        {
            return -1;
        }
        total += ret;
    }
    return 0;
}

int send_n(int sfd, void *buf, int data_len)
{
    char *p = (char *)buf;
    int total = 0, ret;

    while (total < data_len)
    {
        ret = send(sfd, p + total, data_len - total, 0);
        if (ret == -1) // 客户端断开
        {
            return -1;
        }
        total += ret;
    }
    return 0;
}

int tcp_init(int *socket_fd, char *ip, char *port)
{
    // socket
    int sfd;
    sfd = socket(AF_INET, SOCK_STREAM, 0); //初始化一个网络描述符，对应了一个缓冲区
    ERROR_CHECK(sfd, -1, "socket");

    //在bind之前去执行setsockopt,设定端口重用
    int reuse = 1, ret;
    ret = setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(int));
    ERROR_CHECK(ret, -1, "setsockopt");

    struct sockaddr_in ser_addr;
    memset(&ser_addr, 0, sizeof(ser_addr));   //清空
    ser_addr.sin_family = AF_INET;            //代表要进行ipv4通信
    ser_addr.sin_addr.s_addr = inet_addr(ip); //把ip的点分十进制转为网络字节序
    ser_addr.sin_port = htons(atoi(port));    //把端口转为网络字节序

    // bind
    ret = bind(sfd, (struct sockaddr *)&ser_addr, sizeof(ser_addr));
    ERROR_CHECK(ret, -1, "bind");

    // listen 开始监听,端口就开启了
    ret = listen(sfd, 10);
    ERROR_CHECK(ret, -1, "listen");
    *socket_fd = sfd; // socket_fd是一个传出参数
    return 0;
}

int epoll_add(int epfd, int fd)
{
    struct epoll_event event;
    event.events = EPOLLIN; // 注册读事件
    event.data.fd = fd;
    int ret = epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &event);
    ERROR_CHECK(ret, -1, "epoll_ctl");
    return 0;
}